﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using IOP.SgrA;
using IOP.SgrA.SilkNet.Vulkan;
using System.Text.Json;
using System.Numerics;
using System.Runtime.InteropServices;
using Silk.NET.Vulkan;

namespace VkSample52
{
    [ModulePriority(ModulePriority.RenderGroup)]
    public class PipelineModule : VulkanModule, IVulkanRenderWapperModule
    {
        public VulkanRenderWapper RenderWapper { get; set; }

        protected override async Task Load(VulkanGraphicsManager graphicsManager)
        {
            var lDevice = graphicsManager.VulkanDevice;
            var pipeCache = lDevice.PipelineCache;
            var disapatcher = RenderWapper.GetRenderDispatcher<VulkanSwapchainRenderDispatcher>();
            var pass = disapatcher.GetSwapchainRenderPass();
            var area = RenderWapper.RenderArea;
            var basePath = AppContext.BaseDirectory;
            var shaders = Path.Combine(basePath, "Assets", "Shaders");

            var command = lDevice.CreateCommandPool((device, option) =>
            {
                option.Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit;
                option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
            }).CreatePrimaryCommandBuffer();
            var fence = lDevice.CreateFence();
            var mainGroup = graphicsManager.CreatePrimaryRenderGroup("MainRenderGroup", null)
                .Binding(command, lDevice, pass, new Semaphore[] { disapatcher.ImageAvailableSemaphore, disapatcher.RenderFinishSemaphore }, new Fence[] { fence.RawHandle });
            mainGroup.CreateGroupRenderingAction((builder) => builder.UseMiddleware<MainGroupRenderingMiddleware>());
            RenderWapper.CreateAndCutScene<Scene>("main", mainGroup, typeof(TransformSystem));

            VulkanShaderInfo[] infos = new VulkanSPVShaderInfo[]
            {
                new VulkanSPVShaderInfo(ShaderTypes.VertexShader, "commonVert", new FileInfo(Path.Combine(shaders,"vert.spv"))),
                new VulkanSPVShaderInfo(ShaderTypes.FragmentShader, "commonFrag", new FileInfo(Path.Combine(shaders, "frag.spv")))
            };
            await Task.WhenAll(infos[0].LoadShader(), infos[1].LoadShader());

            JsonDocument json = await JsonDocument.ParseAsync(File.OpenRead(Path.Combine(shaders, "pipeline.json")));
            var setLayoutJson = json.RootElement.GetProperty("DescriptorSetLayoutCreateOptions").ToString();
            var layoutJson = json.RootElement.GetProperty("PipelineLayoutCreateOption").ToString();
            var pipelineJson = json.RootElement.GetProperty("Pipeline").ToString();
            GraphicsPipelineCreateOption pipelineOption = JsonSerializer.Deserialize<GraphicsPipelineCreateOption>(pipelineJson);
            var pipelineCache = lDevice.PipelineCache;
            var pipeline = graphicsManager.CreateEmptyPipeline($"MainPipline")
                .BuildPipelineLayoutFromJson(setLayoutJson, layoutJson)
                .BuildUniformBuffer(0, 0, sizeof(float) * 4, SharingMode.Exclusive)
                .BuildGraphicsPipeline(mainGroup.RenderPass, pipelineCache, infos, pipelineOption);
            var sCommand = lDevice.CreateCommandPool((device, option) =>
            {
                option.Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit;
                option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
            }).CreateSecondaryCommandBuffer();
            var aspect = (float)area.Width / area.Height;
            var secondary = graphicsManager.CreateSecondaryVulkanRenderGroup("BallGroup", pipeline)
                .Binding(mainGroup, sCommand, new VulkanSemaphore[0], new VulkanFence[0]);

            var camera = new Camera(new Vector3(0, 0, 30.0f), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            camera.SetProjectMatrix(-aspect, 1.0f, 20.0f, 1000);
            secondary.SetCamera(camera);
            secondary.CreateGroupRenderingAction((builder) => builder.UseMiddleware<SecondaryRenderingMiddleware>());
            secondary.CreateItemRenderingAction((builder) => builder.UseMiddleware<GroupItemMiddleware>());
            secondary.CreateInitializationAction((group) =>
            {
                var uBuffer = group.Pipeline.GetPipelineTexture(0, 0);
                var ambient = new Ambient(0.2f, 0.2f, 0.2f, 0.2f);
                Span<float> uData = stackalloc float[]
                {
                    0.2f, 0.2f, 0.2f, 0.2f
                };
                var bData = MemoryMarshal.AsBytes(uData);
                uBuffer.UpdateTextureMemoryData(bData);
            });
        }

        protected override Task Unload(VulkanGraphicsManager manager)
        {
            return Task.CompletedTask;
        }
    }
}
